#!/usr/bin/env python3
"""
Tests for buildscripts/sbom/*.py
"""

import json
import logging
import os
import sys
import unittest

sys.path.append("buildscripts/sbom")

from buildscripts.sbom.config import get_semver_from_release_version, regex_semver
from buildscripts.sbom.endorctl_utils import EndorCtl
from buildscripts.sbom.generate_sbom import is_valid_purl

logging.basicConfig(level=logging.INFO, stream=sys.stdout)


class TestEndorctl(unittest.TestCase):
    def test_endorctl_init(self):
        """Tests the Endorctl constructor."""
        e = EndorCtl(namespace="mongodb.10gen", retry_limit=1, sleep_duration=5)
        self.assertEqual(e.namespace, "mongodb.10gen")
        self.assertEqual(e.retry_limit, 1)
        self.assertEqual(e.sleep_duration, 5)

    def test_call_endorctl_missing(self):
        """Tests EndorCtl execution with endorctl not in path."""
        logger = logging.getLogger("generate_sbom")
        logger.setLevel(logging.INFO)

        e = EndorCtl(namespace="mongodb.10gen", endorctl_path="this_path_does_not_exist")
        result = e.get_sbom_for_project("https://github.com/10gen/mongo.git")
        self.assertRaises(FileNotFoundError)
        self.assertIsNone(result, None)


class TestConfigRegex(unittest.TestCase):
    def test_semver_regex(self):
        """Tests the regex_semver."""

        # List of valid semantic version strings
        valid_semvers = [
            "0.0.1",
            "1.2.3",
            "10.20.30",
            "1.2.3-alpha",
            "1.2.3-alpha.1",
            "1.2.3-0.beta",
            "1.2.3+build.123",
            "1.2.3-rc.1+build.456",
            "1.0.0-beta+exp.sha.5114f85",
        ]

        # List of invalid semantic version strings
        invalid_semvers = [
            "1.2",  # Incomplete
            "1",  # Incomplete
            "v1.2.3",  # Has a 'v' prefix (regex is for the version part only)
            "1.2.3-",  # Trailing hyphen in pre-release
            "1.2.3+",  # Trailing plus in build
            "1.02.3",  # Leading zero in minor component
            "1.2.03",  # Leading zero in patch component
            "alpha",  # Not a valid version
            "1.2.3.4",  # Four components (SemVer is 3)
            "1.2.3-alpha_beta",  # Underscore in pre-release
        ]

        print("\nTesting regex_semver:")
        for v in valid_semvers:
            with self.subTest(v=v):
                self.assertIsNotNone(
                    regex_semver.fullmatch(v), f"Expected '{v}' to be a valid semver"
                )

        for v in invalid_semvers:
            with self.subTest(v=v):
                self.assertIsNone(
                    regex_semver.fullmatch(v), f"Expected '{v}' to be an invalid semver"
                )

    def test_get_semver_from_release_version(self):
        """Tests the transformation function that uses VERSION_PATTERN_REPL."""

        # (input, expected_output)
        test_cases = [
            # Pattern 1: 'debian/1.28.1-1'
            ("debian/1.28.1-1", "1.28.1"),
            ("debian/1.2.3-rc.1-2", "1.2.3-rc.1"),
            # Pattern 2: 'gperftools-2.9.1', 'mongo/v1.5.2', etc.
            ("gperftools-2.9.1", "2.9.1"),
            ("mongo/v1.5.2", "1.5.2"),
            ("mongodb-8.2.0-alpha2", "8.2.0-alpha2"),
            ("release-1.12.0", "1.12.0"),
            ("yaml-cpp-0.6.3", "0.6.3"),
            ("mongo/1.2.3-beta+build", "1.2.3-beta+build"),
            # Pattern 3: 'asio-1-34-2', 'cares-1_27_0'
            ("asio-1-34-2", "1.34.2"),
            ("cares-1_27_0", "1.27.0"),
            # Pattern 4: 'pcre2-10.40'
            ("pcre2-10.40", "10.40"),
            ("something-1.2", "1.2"),
            # Pattern 5: 'icu-release-57-1'
            ("icu-release-57-1", "57.1"),
            ("foo-bar-12-3", "12.3"),
            # Pattern 6: 'v2.6.0'
            ("v2.6.0", "2.6.0"),
            ("v1.2.3-alpha.1", "1.2.3-alpha.1"),
            # Pattern 7: 'r2.5.1'
            ("r2.5.1", "2.5.1"),
            ("r1.2.3-alpha.1", "1.2.3-alpha.1"),
            # Pattern 7: 'v2025.04.21.00' (non-semver but specific pattern)
            ("v2025.04.21.00", "2025.04.21.00"),
            # --- Cases that should not match ---
            ("1.2.3", "1.2.3"),  # Already clean
            ("latest", "latest"),  # No match
            ("not-a-version", "not-a-version"),  # No match
            ("v1.2", "v1.2"),  # Not matched by any pattern
        ]

        print("\nTesting get_semver_from_release_version():")
        for input_str, expected_str in test_cases:
            with self.subTest(input=input_str):
                result = get_semver_from_release_version(input_str)
                self.assertEqual(
                    result,
                    expected_str,
                    f"Input: '{input_str}', Expected: '{expected_str}', Got: '{result}'",
                )

    def test_purls_valid(self):
        """Tests valid PURLs."""
        valid_purls = [
            "pkg:github/gperftools/gperftools@gperftools-2.9.1",
            "pkg:github/mongodb/mongo-c-driver@1.23.4",
            "pkg:github/google/benchmark",  # No version
            "pkg:github/c-ares/c-ares@cares-1_27_0",
            "pkg:github/apache/avro@release-1.12.0",
            "pkg:github/jbeder/yaml-cpp@yaml-cpp-0.6.3",
            "pkg:github/pcre2project/pcre2@pcre2-10.40",
            "pkg:github/unicode-org/icu@icu-release-57-1",
            "pkg:github/confluentinc/librdkafka@v2.6.0",
            "pkg:github/facebook/folly@v2025.04.21.00?foo=bar#src/main",  # With qualifiers/subpath
            "pkg:generic/valgrind/valgrind@3.23.0",  # namespace/name@version
            "pkg:generic/intel/IntelRDFPMathLib@2.0u2",
            "pkg:generic/openldap/openldap",  # namespace/name
            "pkg:generic/openssl@3.0.13",  # name@version
            "pkg:generic/my-package",  # name only
            "pkg:generic/my-package@1.2.3?arch=x86_64#README.md",  # With qualifiers/subpath
            "pkg:deb/debian/firefox-esr@128.11.0esr-1?arch=source",
            "pkg:pypi/ocspbuilder@0.10.2",
        ]

        print("\nTesting Valid PURLs:")
        for purl in valid_purls:
            with self.subTest(purl=purl):
                self.assertTrue(is_valid_purl(purl), f"Expected '{purl}' to be valid")

    def test_purls_invalid(self):
        """Tests invalid PURLs."""
        invalid_purls = [
            "pkg:github/gperftools",  # Missing name
            "pkg:github/",  # Missing namespace and name
            "pkg:c/github.com/abseil/abseil-cpp",  # Wrong type (from your config.py)
            "pkg:github/mongodb/mongo-c-driver@1.2.3@4.5.6",  # Double version
            "pkg:generic/github/mongodb/mongo",  # Wrong type
            "pkg:generic/",  # Missing name
            "pkg:github/valgrind/",  # Missing name
            "pkg:generic/my-package@1.2@3.4",  # Double version
            "pkg:generic/spaces in name",  # Spaces not allowed (must be encoded)
            "pkg:deb/firefox-esr@128.11.0esr-1?arch=source",  # Missing vendor
            "pkg:pypi/ocsp/ocspbuilder@0.10.2",  # no namespace for PyPI
        ]

        print("\nTesting Invalid PURLs:")
        for purl in invalid_purls:
            with self.subTest(purl=purl):
                self.assertFalse(is_valid_purl(purl), f"Expected '{purl}' to be invalid")


__unittest = True


class TestMetadataFile(unittest.TestCase):
    TEST_DIR = os.path.join("buildscripts", "sbom")
    VERSION_TAG = "{{VERSION}}"

    def read_sbom_json_file(self, file_path: str) -> dict:
        """Load a JSON SBOM file (schema is not validated)"""
        with open(file_path, "r", encoding="utf-8") as input_json:
            sbom_json = input_json.read()
        return json.loads(sbom_json)

    def test_metadata_sbom_version_tags(self):
        sbom_metadata_file = os.path.join(self.TEST_DIR, "metadata.cdx.json")
        print(sbom_metadata_file)
        meta_bom = self.read_sbom_json_file(sbom_metadata_file)
        for component in meta_bom["components"]:
            with self.subTest(component=component):
                properties = []
                properties.append(component["bom-ref"])
                properties.append(component["version"])
                if "purl" in component:
                    properties.append(component["purl"])
                if "cpe" in component:
                    properties.append(component["cpe"])
                # make sure component has a minimum of bom-ref, version and at least one of purl or cpe
                self.assertGreater(
                    len(properties),
                    2,
                    f"Component must have a minimum of bom-ref, version and at least one of purl or cpe. {properties}",
                )
                # make sure all properites either have version tag or no version tags
                self.assertTrue(
                    all(self.VERSION_TAG in p for p in properties)
                    or all(self.VERSION_TAG not in p for p in properties),
                    f"Component must have version tag '{self.VERSION_TAG}' in all or none of bom-ref, version and purl and/or cpe. {properties})",
                )


if __name__ == "__main__":
    unittest.main(verbosity=2)
